#!/usr/bin/python3
# coding: utf-8
import subprocess
import requests
import sys, os, shutil
import urllib
import tarfile
import parched
import errno
import argparse
import re
from pathlib import Path
try:
    import svn.remote
except ImportError:
    print("svn programı veya modulü eksik!")
    print("Programı yüklemek için:")
    print("mps kur subversion")
    print("Modülü yüklemek için:")
    print("pip install svn")
    quit()

root = os.path.dirname(os.path.abspath(__file__))
share_dir = str(Path.home()) + '/.local/share/pkgconverter'
repo_root = share_dir + "/repos"

# Converter Error
class ConError(Exception):
    pass

# Error message to user
def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

'''
 Source classes
 These classes have following methods:
     search_package: Search for a package, and return True if exists, False if not.
     get_package: Download the given package's PKGBUILD directory.
'''
class Arch():
    def search_package(self, package):
        bashCommand = root + "/asp.sh list-all "
        process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
        output, error = process.communicate()

        output = str(output, 'utf-8').split("\n")
        for entry in output:
            if entry == package:
                return True

        return False

    def get_package(self, package):
        path = "/tmp/" + package
        if os.path.isdir(path):
            shutil.rmtree(path)

        bashCommand = root + "/asp.sh -f checkout " + package
        process = subprocess.Popen(bashCommand.split(), stdout=subprocess.DEVNULL)
        output, error = process.communicate()

        return path + "/trunk"

class Aur():
    def search_package(self, package):
        query_url = "https://aur.archlinux.org/rpc/?v=5&type=search&arg="
        response = requests.get(query_url+package)
        results = response.json().get("results")
        for entry in results:
            if entry.get("Name") == package:
                return True
        return False

    def get_package(self, package):
        source_url = "https://aur.archlinux.org/cgit/aur.git/snapshot/" + package + ".tar.gz"
        tmp_path = "/tmp/" + os.path.basename(source_url)
        # download the package folder under /tmp
        urllib.request.urlretrieve(source_url, tmp_path)
        # extract it to the repo
        with tarfile.open(tmp_path) as tar:
            tar.extractall(path="/tmp")
        # delete the tar file
        os.unlink(tmp_path)
        return  "/tmp/" + package

class Kaos():
    repo_core = "https://github.com/KaOSx/core"
    repo_main = "https://github.com/KaOSx/main"
    repo_apps = "https://github.com/KaOSx/apps"

    def search_package(self, package):
        for repo in (self.repo_core, self.repo_main, self.repo_apps):
            url = repo + "/tree/master/" + package
            response = requests.get(url)
            if response.status_code == 200:
                return True
        return False


    def get_package(self, package):
        link = ""

        for repo in (self.repo_core, self.repo_main, self.repo_apps):
            url = repo + "/tree/master/" + package
            response = requests.get(url)
            if response.status_code == 200:
                link = repo + "/trunk/" + package

        rem = svn.remote.RemoteClient(link)
        rem.checkout('/tmp/' + package)

        # Delete unnecessary directories
        if os.path.isdir('/tmp/' + package + '/.svn'):
            shutil.rmtree('/tmp/' + package + '/.svn')
        if os.path.isdir('/tmp/' + package + '/.git'):
            shutil.rmtree('/tmp/' + package + '/.git')

        return "/tmp/" + package

class Blackarch():
    repo = "https://github.com/BlackArch/blackarch"

    def search_package(self, package):
        url = self.repo + "/tree/master/packages/" + package
        response = requests.get(url)
        if response.status_code == 200:
            return True
        return False


    def get_package(self, package):
        link = self.repo + "/trunk/packages/" + package

        rem = svn.remote.RemoteClient(link)
        rem.checkout('/tmp/' + package)

        # Delete unnecessary directories
        if os.path.isdir('/tmp/' + package + '/.svn'):
            shutil.rmtree('/tmp/' + package + '/.svn')
        if os.path.isdir('/tmp/' + package + '/.git'):
            shutil.rmtree('/tmp/' + package + '/.git')

        return "/tmp/" + package

'''
 Talimat class contains the required information to create talimat directories.
'''
class Talimat():

    def __init__(self):
        self.isim = ""
        self.surum = ""
        self.devir = 1

        self.paket = {}
        self.paket["tanim"] = ""
        self.paket["paketci"] = ""
        self.paket["grup"] = ""
        self.paket["url"] = ""

        self.gerek = {}
        self.gerek["derleme"] = []
        self.gerek["calisma"] = []

        self.kaynak = {}

        self.sha512 = []
        self.sha256 = []

        self.derle = {}

        self.pakur = {}

        # this will be declared in create_dir function
        self.path = ""

    '''
    Creates a directory with the format $isim#$surum-$devir under the
    given path.
    '''
    def create_dir(self, path = root + '/out'):
        dir_name = self.isim + "#" + self.surum + "-" + str(self.devir)
        self.path = path + "/" + dir_name
        try:
            os.mkdir(self.path)
        except FileExistsError:
            # TODO ask user to overwrite this folder
            confirm = input("Böyle bir talimat klasörü zaten var. Değiştirmek istiyor musunuz?[e/H]")
            if confirm == 'e':
                shutil.rmtree(self.path)
                os.mkdir(self.path)
            else:
                print("Klasör oluşturulmadı!")
        except:
            eprint("Verilen dosya yolu bulunamıyor! Klasör oluşturulmadı!")
        return self.path

    '''
    Creates the talimat file under the given dir_path.
    '''
    def create_talimat(self, dir_path):
        file_content = self.create_talimat_content()
        try:
            with open(dir_path + '/talimat', 'w+') as talimat_file:
                talimat_file.write(file_content)
        except:
            raise ConError("Klasör bulunamadı!")

    '''
    Creates the talimat file's content with the fields from the object.
    '''
    def create_talimat_content(self):
        #if not os.path.exists(dir_path):
        #    raise ConError("'talimat' yüklenecek dizin bulunamıyor!")

        file_content = ""

        def add_section(section):
            nonlocal file_content
            file_content += "[" + section + "]\n"

        def add_key(key, value):
            nonlocal file_content
            file_content += key + "  =  " + value +"\n"

        def add_comment(comment):
            nonlocal file_content
            file_content += ";" + comment + "\n"

        def add_line():
            nonlocal file_content
            file_content += "\n"

        # create paket section
        add_section("paket")
        add_key("tanim", self.paket["tanim"])
        add_key("paketci", self.paket["paketci"])
        add_key("grup", self.paket["grup"])
        add_key("url", self.paket["url"])
        add_line()

        # create gerek section
        add_section("gerek")

        # for now, put every dependency under derleme

        derleme_gerek = ""
        for entry in self.gerek["derleme"]:
            derleme_gerek += entry + " "

        for entry in self.gerek["calisma"]:
            derleme_gerek += entry + " "

        add_key("derleme", derleme_gerek)

        calisma_gerek = ""
        add_key("calisma", calisma_gerek)

        add_line()

        # create kaynak section
        add_section("kaynak")
        for k,v in self.kaynak.items():
            # in case if key is given as number
            add_key(str(k), v)
        add_line()

        # TODO create sha section
        if self.sha512:
            add_section("sha512")
            for i in range(len(self.sha512)):
                # range() starts from 0, but sha512 starts from 1
                add_key(str(i+1), self.sha512[i])
            add_line()
        elif self.sha256:
            add_section("sha256")
            for i in range(len(self.sha256)):
                # range() starts from 0, but sha256 starts from 1
                add_key(str(i+1), self.sha256[i])
            add_line()

        # create derle section
        add_section("derle")
        for k,v in self.derle.items():
            if k == "betik":
                for betik in v.split("\n"):
                    betik = betik.strip(" ")
                    add_key("betik", betik)
            else:
                add_key(k, v)
        add_line()

        # create pakur section
        add_section("pakur")
        for k,v in self.pakur.items():
            if k == "betik":
                for betik in v.split("\n"):
                    betik = betik.strip(" ")
                    add_key("betik", betik)
            else:
                add_key(k, v)
        add_line()

        return file_content


# TODO create /out directory initially

# Parsing PKGBUILD
test_src = root+"/repos/aur/polybar"
test_pkgbuild = test_src+"/PKGBUILD"
#print(test_pkgbuild)

class Converter:
    '''
    A class containing all the converting methods needed.
    parse()
    transfer_files()
    create_script_content()
    con_to_talimat()
    '''

    def parse(self,file_path):
        '''
        Return a parched.PKGBUILD object containing following values:

            name            str
            version         str
            release         str
            description     str
            url             str
            licenses        []
            groups          []
            provides        []
            depends         []
            optdepends      []
            conflicts       []
            replaces        []
            architectures   []
            backup          []
            options         []
            functions       {}
            install         str
            checksums       {'md5', 'sha1', 'sha256', 'sha384', 'sha512'}, subgroups are []
            noextract       []
            sources         []
            makedepends     []
        '''
        package = parched.PKGBUILD(file_path)
        return package

    '''
    Takes a package path and a talimat path as arguments, and transfers the files of packages to
    talimat path except PKGBUILD file.
    '''
    def transfer_files(self, pkg_path, tal_path):
        # TODO add directory support
        for entry in os.listdir(pkg_path):
            if entry != 'PKGBUILD' and entry != '.SRCINFO' and not entry.endswith(".install"):
                shutil.copy(pkg_path + "/" + entry, tal_path)


    '''
    Takes package, tal_path and script_type as arguments and creates appropriate script files
    for that package in given type (derle or pakur) under given tal_path.
    '''
    def create_script_content(self, package, talimat, tal_path, script_type):
        if script_type not in ("derle", "pakur"):
            raise ConError("Script stili yanlış!")

        script_content = ""
        if script_type == "derle":
            if "prepare" in package.functions.keys():
                script_content = package.functions["prepare"]
            if "build" in package.functions.keys():
                script_content += package.functions["build"]
        elif script_type == "pakur":
            if "package" in package.functions.keys():
                script_content = package.functions["package"]
        else:
            raise ConError("Wrong script type!")

        # Edit scripts

        # remove cd $pkgname-$pkgver
        script_content = re.sub(r'[ \t]*cd "?\$\{?pkgname\}?-\$\{?pkgver\}?"?\n', '', script_content)

        # Replace variable names
        script_content = re.sub(r'\$\{?pkgname\}?', '${isim}', script_content)
        script_content = re.sub(r'\$\{?pkgver\}?', '${surum}', script_content)
        script_content = re.sub(r'\$\{?pkgrel\}?', '${devir}', script_content)
        script_content = re.sub(r'\$\{?pkgdir\}?', '${PKG}', script_content)
        script_content = re.sub(r'\$\{?srcdir\}?', '${SRC}', script_content)

        if script_type == "derle":
            tal_section = talimat.derle
        else:
            tal_section = talimat.pakur

        betiks = script_content.split("\n")
        while '' in betiks: betiks.remove('')
        while ' ' in betiks: betiks.remove(' ')
        if len(betiks) == 0:
            # No items here, skip
            return
        if len(betiks) <= 5:
                tal_section["betik"] = "\n".join(betiks)
        else:
            tal_section["dosya"] = script_type + ".sh"
            try:
                with open(tal_path + '/' + script_type +'.sh', 'w+') as file:
                    file.write(script_content)
            except:
                raise ConError("Dosya oluşturulamadı.")

    '''
    Takes an parched.PKGBUILD object as an argument, and creates a Talimat object by
    using its fileds.
    '''
    def con_to_talimat(self, package, dir_path):
        talimat = Talimat()
        # Set the metadata
        talimat.isim = package.name
        talimat.surum = package.version

        # Set the values of the paket section
        talimat.paket["tanim"] = package.description
        #talimat.paket["grup"] = package.groups[0] # Check!!!
        talimat.paket["url"] = package.url

        # Set the values of the gerek section
        for entry in package.makedepends:
            talimat.gerek["derleme"].append(entry)

        for entry in package.depends:
            talimat.gerek["calisma"].append(entry)

        # Set the values of the kaynak section
        for i in range(len(package.sources)):
            # range() starts from 0, but kaynak starts from 1
            if package.sources[i]:
                # if it's a file use 'dosya' keyword
                is_file = False
                for entry in os.listdir(dir_path):
                    if entry == package.sources[i]:
                        is_file = True
                if is_file:
                        talimat.kaynak["dosya"] = package.sources[i]
                else:
                    # otherwise use number notation
                    talimat.kaynak[str(i+1)] = package.sources[i]

        #print(package.checksums["sha256"][0])
        if package.checksums["sha512"]:
            for i in range(len(package.checksums["sha512"])):
                # range() starts from 0, but sha512 starts from 1
                talimat.sha512.append(package.checksums["sha512"][i])
        elif package.checksums["sha256"]:
            for i in range(len(package.checksums["sha256"])):
                # range() starts from 0, but sha256 starts from 1
                talimat.sha256.append(package.checksums["sha256"][i])

        # TODO add betik support
        # TODO add tip support
        # Set the values of the derle section
        # talimat.derle["dosya"] = "derle.sh"

        # Set the values of the pakur section
        # talimat.pakur["dosya"] = "pakur.sh"

        return talimat

def Main():
    # Parse the arguments
    parser = argparse.ArgumentParser(description="Faklı disto kaynaklarındaki paketleri talimat formatına çevir.")

    parser.add_argument("-d", "--dir", type=str, help="talimat dosyalarının oluşturulacağı dizin")
    parser.add_argument("-f", action='store_true', help="bulunan ilk kaynağı seçer")
    parser.add_argument("isim", help="Dönüştürülecek paketin ismi")
    parser.set_defaults(func=convert)

    args = parser.parse_args()
    args.func(args)

    # Print where the directory is created

def convert(args):
    package_name = args.isim

    package_source = ""
    package_dir = "./out"

    # TODO add support for comma separated list

    if args.dir:
        package_dir = args.dir

    if not os.path.isdir(package_dir):
        try:
            os.mkdir(package_dir)
        except:
            raise ConError("Verilen çıktı yolu bulunamıyor!")

    # Delete existing tmp directories to prevent some disgusting errors
    if os.path.isdir('/tmp/' + package_name):
        shutil.rmtree('/tmp/' + package_name)

    results = []

    for source in (Arch(), Aur(), Kaos(), Blackarch()):
        result = source.search_package(package_name)
        if result:
            results.append(source)

    if len(results) == 0:
        print("Herhangi bir eşleşme bulunamadı...")
        quit()

    source_no = None
    if args.f:
        source_no = 0
    else:
        print("Lütfen çeviri için bir kaynak seçin:\n")
        for i in range(len(results)):
            print("[" + str(i) + "] " + type(results[i]).__name__)

        while source_no not in range(len(results)):
            source_no = int(input(">"))

    package = results[int(source_no)].get_package(package_name)
    pkgbuild = package + '/PKGBUILD'
    converter = Converter()
    packt = converter.parse(pkgbuild)
    talimat = converter.con_to_talimat(packt, package)
    path = talimat.create_dir(package_dir)
    converter.create_script_content(packt, talimat, path, "derle")
    converter.create_script_content(packt, talimat, path, "pakur")
    talimat.create_talimat(path)
    converter.transfer_files(package, path)
    print("Tamamlandı.")
    print("Talimat dizini " + path + " altında oluşturuldu.")

if __name__ == "__main__":
    Main()

# TODO delete tmp files after the job

########## Debugging ################
